import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.tree.IFileElementType;
import com.intellij.psi.util.PsiTreeUtil;

import java.text.MessageFormat;

/**
 * Intellij IDEA 插件开发 | 京东云技术团队
 * https://zhuanlan.zhihu.com/p/640099358?utm_id=0
 */
public class BatchToSplitBatchAction extends AnAction {

    @Override
    public void actionPerformed(AnActionEvent e) {
        // TODO: insert action logic here
        Project project = e.getData(PlatformDataKeys.PROJECT);

        // https://gitee.com/pillowtree/scream-tool/blob/master/src/com/best/java/scream/tool/action/BeanToJsonString.java
        // IDEA自定义插件开发总结 https://zhuanlan.zhihu.com/p/408739679?utm_id=0
        // 编辑器就是当前文件里光标可以控制的部分，比如选中文本，弹窗，高亮等
        Editor editor = e.getData(CommonDataKeys.EDITOR);

        Document document = editor.getDocument();

        SelectionModel selectionModel = editor.getSelectionModel();
        String selectedText = selectionModel.getSelectedText();

        if (selectedText == null || selectedText.trim().length() == 0) {
            // 未选择任何语句
            Messages.showMessageDialog(project, "未选择任何语句", "无法转换", Messages.getInformationIcon());
            return;
        }

        System.out.println(selectedText);

        // 编写一个IDEA插件之：自动生成Java代码 https://zhuanlan.zhihu.com/p/265301281
        // 判断当前文件是否是一个Java文件
        PsiFile psiFile = e.getData(LangDataKeys.PSI_FILE);
        // java.lang.NoClassDefFoundError: com/intellij/psi/PsiJavaFile
        if (psiFile == null || !(psiFile instanceof PsiJavaFile) || !psiFile.getVirtualFile().getName().contains(".java")) {
            // 不是JAVA类直接返回
            Messages.showMessageDialog(project, "不是java文件", "无法转换", Messages.getInformationIcon());
            return;
        }
        // 只读文件直接返回
        FileType fileType = psiFile.getFileType(); // fileType.getName()
        if(fileType.isReadOnly()){
            Messages.showMessageDialog(project, "文件只读", "无法转换", Messages.getInformationIcon());
            return;
        }
        IFileElementType elementType = psiFile.getFileElementType(); // elementType.getLanguage().myID


        // 获取方法名
        // 获取当前光标所在的PsiElement
        // 存在问题：如果将光标放在一行代码的;后面，那么该方法就会返回null值
        PsiElement psiElement = e.getData(LangDataKeys.PSI_ELEMENT);

        // 另一种方法：查找当前光标停留在的元素
        // PsiElement psiElement = psiFile.findElementAt(editor.getCaretModel().getOffset());

        // java - 在 Intellij IDEA 插件中从 PsiMethod 获取信息 https://www.coder.work/article/3590138
        // https://blog.csdn.net/wang5525369/article/details/127429678
        // NotificationUtil.notifyInfo(message, project); 是类，不是方法
        // .notifyInfo(message, project); 是方法
        PsiParameter[] parameterArr = null;
        // PSI元素 https://www.jianshu.com/p/8ab8efdfc647
        /* 怎样得到一个PSI元素？
        操作：e.getData(LangDataKeys.PSI_ELEMENT)；
        注意：如果当前编辑器打开并且光标所在元素是引用，这将返回解析引用的结果。 这可能不是你所需要的。
        文件偏移: PsiFile.findElementAt()；
        注意：这返回指定偏移量最低级别的元素，通常是一个词法记号。大多数情况，你需要使用PsiTreeUtil.getParentOfType() 找出你真正需要的元素*/
        if (!(psiElement instanceof PsiMethod)) {
            if (psiElement != null) {
                // System.out.println("getData: " + psiElement.getText() + ", " + psiElement.getStartOffsetInParent());
            }
            int indexOf = selectedText.indexOf(".");

            psiElement = psiFile.findElementAt(selectionModel.getSelectionStart() + indexOf + 2);

            /*
            光标是取的最后点击右键的位置 特不靠谱
            // 选择光标最后所在位置 findElementAt查回的元素和getData有区别
            // findElementAt查回是词法记号 PsiIdentifier:parseInt
            // 光标位置 最后在哪儿点击就在哪儿吗？？？？？
            PsiElement elementAt = psiFile.findElementAt(editor.getCaretModel().getOffset());
            System.out.println(elementAt.getText() + ", " + editor.getCaretModel().getOffset()); // 3100 Integer

            // PsiWhiteSpace 最后是一大段空格 光标所在位置定不准
            if ("".equals(elementAt.getText())) {
                // 从前往后选中 光标在最后面
                psiElement = psiFile.findElementAt(editor.getCaretModel().getOffset() - selectedText.length() + indexOf + 2);//3109 parseInt
            } else {
                psiElement = psiFile.findElementAt(editor.getCaretModel().getOffset() + indexOf + 2);//3109 parseInt
            }
            System.out.println(psiElement.getText() + ", " + (editor.getCaretModel().getOffset() + indexOf + 2) + ", " + psiElement.getStartOffsetInParent());*/

            // 可以调用getReference获取到该元素的PsiReference实例，最后调用PsiReference实例的resolve方法取得该方法调用表达式元素链接到的声明是一个PsiMethod
            // https://blog.csdn.net/weixin_39867125/article/details/111209632
            PsiElement resolve = psiElement.getParent().getReference().resolve();

            // http://www.yiidian.com/sources/java_source/com.intellij.psi.PsiJavaCodeReferenceElement.html
            PsiJavaCodeReferenceElement parentOfType2 = PsiTreeUtil.getParentOfType(psiElement, PsiJavaCodeReferenceElement.class);
            if (parentOfType2 != null) {
                psiElement = parentOfType2.resolve();
            }

            // 获取当前方法的PsiCodeBlock元素
            /*PsiElement codeBlock = psiElement;
            while (!(codeBlock instanceof PsiCodeBlock)) {
                codeBlock = codeBlock.getParent();
            }*/

            System.out.println();
        }

        if (psiElement instanceof PsiMethod) {
            // dao.batchDelete(list);
            // 方法签名 参数类型 参数名称
            PsiMethod method = (PsiMethod) psiElement;
            String methodName = method.getName();
            System.out.println(methodName); // 方法名称 batchDelete
            PsiParameterList parameterList = method.getParameterList();
            PsiTypeParameter[] typeParameters = method.getTypeParameters();
            if (typeParameters.length > 0) {
                PsiAnnotation[] annotations = typeParameters[0].getAnnotations();
            }

            int parametersCount = parameterList.getParametersCount(); // 参数个数

            parameterArr = parameterList.getParameters();
            int listTypeIndex = -1; // list类型参数下标
            for (int i = 0; i < parameterArr.length; i++) {
                PsiParameter param = parameterArr[i];
                System.out.println(param.getType().getPresentableText()); // List<TestTable01>
                System.out.println(param.getType().getCanonicalText()); // java.util.List<com.ycxh.module.mybatis.TestTable01>
                System.out.println(param.getName()); // list

                // 方法参数必须包含List类型
                if (param.getType().getCanonicalText().startsWith("java.util.List")) {
                    if (listTypeIndex < 0) {
                        listTypeIndex = i;
                    } else {
                        // 存在多个list参数，无法转换
                        Messages.showMessageDialog(project, "存在多个list类型参数", "无法转换", Messages.getInformationIcon());
                        return;
                    }
                }

            }
            if (listTypeIndex < 0) {
                Messages.showMessageDialog(project, "不存在list类型参数", "无法转换", Messages.getInformationIcon());
                return;
            }

            System.out.println(method.getReturnType()); // 方法返回值 PsiType:int


            // 获取光标选择的文本，并替换成hello
            // SelectionModel getSelectionStart();  // 返回所选文本范围的文档中的起始偏移量
            // SelectionModel getSelectionEnd(); // 返回所选文本范围的文档中的结尾偏移量
            // WriteCommandAction.runWriteCommandAction(project, () ->
            //         document.replaceString(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd(), "hello"));

            // 要实现的效果：
            // 将批量插入方法改写成分批批量插入
            // dao.batchUpdateSex(list, 2);
            // 转换为
            // batchHandle(list, t1 -> dao.batchUpdateSex(t1, 2));
            // selectedText = selectedText.trim(); // 不要改变空格、缩进

            int index = selectedText.indexOf(methodName);
            if (index < 0) {
                Messages.showMessageDialog(project, "未选中完整的方法", "无法转换", Messages.getInformationIcon());
                return;
            }

            int selectionStart = selectionModel.getSelectionStart();
            int selectionEnd = selectionModel.getSelectionEnd();
            if (index == 0) {
                // 需要判断方法前面是否存在调用类
                PsiElement psiElementTmp = psiFile.findElementAt(selectionStart - 2);

                String text = document.getText(new TextRange(selectionStart - 2, selectionStart));
                if (text.indexOf(".") >= 0) {
                    Messages.showMessageDialog(project, "未选中完整的方法", "无法转换", Messages.getInformationIcon());
                    return;
                }
            }


            int indexOf = selectedText.indexOf("(");
            if (indexOf == -1) {
                Messages.showMessageDialog(project, "不存在(号", "无法转换", Messages.getInformationIcon());
                return;
            }
            String methodStr = selectedText.substring(0, indexOf);
            // System.out.println(methodStr);
            String end = selectedText.substring(indexOf + 1);
            indexOf = end.indexOf("(");
            if (indexOf != -1) {
                Messages.showMessageDialog(project, "存在多个(号", "无法转换", Messages.getInformationIcon());
                return;
            }

            int indexOfEnd = end.indexOf(")");
            if (indexOfEnd == -1) {
                Messages.showMessageDialog(project, "不存在)号", "无法转换", Messages.getInformationIcon());
                return;
            }
            String parameterStr = end.substring(0, indexOfEnd);
            // System.out.println(parameterStr);

            String[] parameters = parameterStr.split(",");
            String replace = "";

            if (parametersCount != parameters.length) {
                Messages.showMessageDialog(project, "参数个数不匹配", "无法转换", Messages.getInformationIcon());
                return;
            }

            if (parameters.length == 1) {
                replace = MessageFormat.format("batchHandle({0}, t -> {1}(t))", parameters[0], methodStr);

            } else {
                String parameterTmp = parameters[listTypeIndex]; // 原始参数名
                parameters[listTypeIndex] = " t"; // 修改为形参名

                replace = MessageFormat.format("batchHandle({0}, t -> {1}({2}))", parameterTmp, methodStr, String.join(",", parameters));

                // 想太多版本，多个参数，参数类型转义
                // String methodParameters = "t1";
                // // 参数转义需要手动补全
                // if (parameters.length >= 2) {
                //     String typeName = parameterArr != null && parameterArr.length >= 2 ? parameterArr[1].getType().getPresentableText() : "";
                //     methodParameters += ", (" + typeName + ")t2";
                // } else {
                //     parameterStr += ", null";
                // }
                // if (parameters.length >= 3) {
                //     String typeName = parameterArr != null && parameterArr.length >= 3 ? parameterArr[2].getType().getPresentableText() : "";
                //     methodParameters += ", (" + typeName + ")t3";
                // } else {
                //     parameterStr += ", null";
                // }

                // replace = MessageFormat.format("batchHandle({0}, (t1, t2, t3) -> {1}({2}));", parameterStr, methodStr, methodParameters);
            }


            indexOfEnd = methodStr.length() + indexOfEnd + 2;
            if (selectedText.length() > indexOfEnd) {
                replace += selectedText.substring(indexOfEnd);
            }
            final String finalReplace = replace;
            WriteCommandAction.runWriteCommandAction(project, () ->
                    document.replaceString(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd(), finalReplace));


        } else {
            Messages.showMessageDialog(project, "获取方法元素失败", "无法转换", Messages.getInformationIcon());
            return;
        }

    }
}
